<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>

                    <h4>函数的参数</h4>
                    <div class="x-wiki-info"><span>3271次阅读</span></div>
                    <hr style="border-top-color:#ccc" />
                    <div class="x-wiki-content x-content"><p>定义函数的时候，我们把参数的名字和位置确定下来，函数的接口定义就完成了。对于函数的调用者来说，只需要知道如何传递正确的参数，以及函数将返回什么样的值就够了，函数内部的复杂逻辑被封装起来，调用者无需了解。</p>
<p>Python的函数定义非常简单，但灵活度却非常大。除了正常定义的必选参数外，还可以使用默认参数、可变参数和关键字参数，使得函数定义出来的接口，不但能处理复杂的参数，还可以简化调用者的代码。</p>
<h3 id="-">默认参数</h3>
<p>我们仍以具体的例子来说明如何定义函数的默认参数。先写一个计算x<sup>2</sup>的函数：</p>
<pre><code>def power(x):
    return x * x
</code></pre><p>当我们调用<code>power</code>函数时，必须传入有且仅有的一个参数<code>x</code>：</p>
<pre><code>&gt;&gt;&gt; power(5)
25
&gt;&gt;&gt; power(15)
225
</code></pre><p>现在，如果我们要计算x<sup>3</sup>怎么办？可以再定义一个<code>power3</code>函数，但是如果要计算x<sup>4</sup>、x<sup>5</sup>……怎么办？我们不可能定义无限多个函数。</p>
<p>你也许想到了，可以把<code>power(x)</code>修改为<code>power(x, n)</code>，用来计算x<sup>n</sup>，说干就干：</p>
<pre><code>def power(x, n):
    s = 1
    while n &gt; 0:
        n = n - 1
        s = s * x
    return s
</code></pre><p>对于这个修改后的<code>power</code>函数，可以计算任意n次方：</p>
<pre><code>&gt;&gt;&gt; power(5, 2)
25
&gt;&gt;&gt; power(5, 3)
125
</code></pre><p>但是，旧的调用代码失败了，原因是我们增加了一个参数，导致旧的代码无法正常调用：</p>
<pre><code>&gt;&gt;&gt; power(5)
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
TypeError: power() takes exactly 2 arguments (1 given)
</code></pre><p>这个时候，默认参数就排上用场了。由于我们经常计算x<sup>2</sup>，所以，完全可以把第二个参数n的默认值设定为2：</p>
<pre><code>def power(x, n=2):
    s = 1
    while n &gt; 0:
        n = n - 1
        s = s * x
    return s
</code></pre><p>这样，当我们调用<code>power(5)</code>时，相当于调用<code>power(5, 2)</code>：</p>
<pre><code>&gt;&gt;&gt; power(5)
25
&gt;&gt;&gt; power(5, 2)
25
</code></pre><p>而对于<code>n &gt; 2</code>的其他情况，就必须明确地传入n，比如<code>power(5, 3)</code>。</p>
<p>从上面的例子可以看出，默认参数可以简化函数的调用。设置默认参数时，有几点要注意：</p>
<p>一是必选参数在前，默认参数在后，否则Python的解释器会报错（思考一下为什么默认参数不能放在必选参数前面）；</p>
<p>二是如何设置默认参数。</p>
<p>当函数有多个参数时，把变化大的参数放前面，变化小的参数放后面。变化小的参数就可以作为默认参数。</p>
<p>使用默认参数有什么好处？最大的好处是能降低调用函数的难度。</p>
<p>举个例子，我们写个一年级小学生注册的函数，需要传入<code>name</code>和<code>gender</code>两个参数：</p>
<pre><code>def enroll(name, gender):
    print &#39;name:&#39;, name
    print &#39;gender:&#39;, gender
</code></pre><p>这样，调用<code>enroll()</code>函数只需要传入两个参数：</p>
<pre><code>&gt;&gt;&gt; enroll(&#39;Sarah&#39;, &#39;F&#39;)
name: Sarah
gender: F
</code></pre><p>如果要继续传入年龄、城市等信息怎么办？这样会使得调用函数的复杂度大大增加。</p>
<p>我们可以把年龄和城市设为默认参数：</p>
<pre><code>def enroll(name, gender, age=6, city=&#39;Beijing&#39;):
    print &#39;name:&#39;, name
    print &#39;gender:&#39;, gender
    print &#39;age:&#39;, age
    print &#39;city:&#39;, city
</code></pre><p>这样，大多数学生注册时不需要提供年龄和城市，只提供必须的两个参数：</p>
<pre><code>&gt;&gt;&gt; enroll(&#39;Sarah&#39;, &#39;F&#39;)
Student:
name: Sarah
gender: F
age: 6
city: Beijing
</code></pre><p>只有与默认参数不符的学生才需要提供额外的信息：</p>
<pre><code>enroll(&#39;Bob&#39;, &#39;M&#39;, 7)
enroll(&#39;Adam&#39;, &#39;M&#39;, city=&#39;Tianjin&#39;)
</code></pre><p>可见，默认参数降低了函数调用的难度，而一旦需要更复杂的调用时，又可以传递更多的参数来实现。无论是简单调用还是复杂调用，函数只需要定义一个。</p>
<p>有多个默认参数时，调用的时候，既可以按顺序提供默认参数，比如调用<code>enroll(&#39;Bob&#39;, &#39;M&#39;, 7)</code>，意思是，除了<code>name</code>，<code>gender</code>这两个参数外，最后1个参数应用在参数<code>age</code>上，<code>city</code>参数由于没有提供，仍然使用默认值。</p>
<p>也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时，需要把参数名写上。比如调用<code>enroll(&#39;Adam&#39;, &#39;M&#39;, city=&#39;Tianjin&#39;)</code>，意思是，<code>city</code>参数用传进去的值，其他默认参数继续使用默认值。</p>
<p>默认参数很有用，但使用不当，也会掉坑里。默认参数有个最大的坑，演示如下：</p>
<p>先定义一个函数，传入一个list，添加一个<code>END</code>再返回：</p>
<pre><code>def add_end(L=[]):
    L.append(&#39;END&#39;)
    return L
</code></pre><p>当你正常调用时，结果似乎不错：</p>
<pre><code>&gt;&gt;&gt; add_end([1, 2, 3])
[1, 2, 3, &#39;END&#39;]
&gt;&gt;&gt; add_end([&#39;x&#39;, &#39;y&#39;, &#39;z&#39;])
[&#39;x&#39;, &#39;y&#39;, &#39;z&#39;, &#39;END&#39;]
</code></pre><p>当你使用默认参数调用时，一开始结果也是对的：</p>
<pre><code>&gt;&gt;&gt; add_end()
[&#39;END&#39;]
</code></pre><p>但是，再次调用<code>add_end()</code>时，结果就不对了：</p>
<pre><code>&gt;&gt;&gt; add_end()
[&#39;END&#39;, &#39;END&#39;]
&gt;&gt;&gt; add_end()
[&#39;END&#39;, &#39;END&#39;, &#39;END&#39;]
</code></pre><p>很多初学者很疑惑，默认参数是<code>[]</code>，但是函数似乎每次都“记住了”上次添加了<code>&#39;END&#39;</code>后的list。</p>
<p>原因解释如下：</p>
<p>Python函数在定义的时候，默认参数<code>L</code>的值就被计算出来了，即<code>[]</code>，因为默认参数<code>L</code>也是一个变量，它指向对象<code>[]</code>，每次调用该函数，如果改变了<code>L</code>的内容，则下次调用时，默认参数的内容就变了，不再是函数定义时的<code>[]</code>了。</p>
<p>所以，定义默认参数要牢记一点：默认参数必须指向不变对象！</p>
<p>要修改上面的例子，我们可以用<code>None</code>这个不变对象来实现：</p>
<pre><code>def add_end(L=None):
    if L is None:
        L = []
    L.append(&#39;END&#39;)
    return L
</code></pre><p>现在，无论调用多少次，都不会有问题：</p>
<pre><code>&gt;&gt;&gt; add_end()
[&#39;END&#39;]
&gt;&gt;&gt; add_end()
[&#39;END&#39;]
</code></pre><p>为什么要设计str、None这样的不变对象呢？因为不变对象一旦创建，对象内部的数据就不能修改，这样就减少了由于修改数据导致的错误。此外，由于对象不变，多任务环境下同时读取对象不需要加锁，同时读一点问题都没有。我们在编写程序时，如果可以设计一个不变对象，那就尽量设计成不变对象。</p>
<h3 id="-">可变参数</h3>
<p>在Python函数中，还可以定义可变参数。顾名思义，可变参数就是传入的参数个数是可变的，可以是1个、2个到任意个，还可以是0个。</p>
<p>我们以数学题为例子，给定一组数字a，b，c……，请计算a<sup>2</sup> + b<sup>2</sup> + c<sup>2</sup> + ……。</p>
<p>要定义出这个函数，我们必须确定输入的参数。由于参数个数不确定，我们首先想到可以把a，b，c……作为一个list或tuple传进来，这样，函数可以定义如下：</p>
<pre><code>def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
</code></pre><p>但是调用的时候，需要先组装出一个list或tuple：</p>
<pre><code>&gt;&gt;&gt; calc([1, 2, 3])
14
&gt;&gt;&gt; calc((1, 3, 5, 7))
84
</code></pre><p>如果利用可变参数，调用函数的方式可以简化成这样：</p>
<pre><code>&gt;&gt;&gt; calc(1, 2, 3)
14
&gt;&gt;&gt; calc(1, 3, 5, 7)
84
</code></pre><p>所以，我们把函数的参数改为可变参数：</p>
<pre><code>def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
</code></pre><p>定义可变参数和定义list或tuple参数相比，仅仅在参数前面加了一个<code>*</code>号。在函数内部，参数<code>numbers</code>接收到的是一个tuple，因此，函数代码完全不变。但是，调用该函数时，可以传入任意个参数，包括0个参数：</p>
<pre><code>&gt;&gt;&gt; calc(1, 2)
5
&gt;&gt;&gt; calc()
0
</code></pre><p>如果已经有一个list或者tuple，要调用一个可变参数怎么办？可以这样做：</p>
<pre><code>&gt;&gt;&gt; nums = [1, 2, 3]
&gt;&gt;&gt; calc(nums[0], nums[1], nums[2])
14
</code></pre><p>这种写法当然是可行的，问题是太繁琐，所以Python允许你在list或tuple前面加一个<code>*</code>号，把list或tuple的元素变成可变参数传进去：</p>
<pre><code>&gt;&gt;&gt; nums = [1, 2, 3]
&gt;&gt;&gt; calc(*nums)
14
</code></pre><p>这种写法相当有用，而且很常见。</p>
<h3 id="-">关键字参数</h3>
<p>可变参数允许你传入0个或任意个参数，这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装为一个dict。请看示例：</p>
<pre><code>def person(name, age, **kw):
    print &#39;name:&#39;, name, &#39;age:&#39;, age, &#39;other:&#39;, kw
</code></pre><p>函数<code>person</code>除了必选参数<code>name</code>和<code>age</code>外，还接受关键字参数<code>kw</code>。在调用该函数时，可以只传入必选参数：</p>
<pre><code>&gt;&gt;&gt; person(&#39;Michael&#39;, 30)
name: Michael age: 30 other: {}
</code></pre><p>也可以传入任意个数的关键字参数：</p>
<pre><code>&gt;&gt;&gt; person(&#39;Bob&#39;, 35, city=&#39;Beijing&#39;)
name: Bob age: 35 other: {&#39;city&#39;: &#39;Beijing&#39;}
&gt;&gt;&gt; person(&#39;Adam&#39;, 45, gender=&#39;M&#39;, job=&#39;Engineer&#39;)
name: Adam age: 45 other: {&#39;gender&#39;: &#39;M&#39;, &#39;job&#39;: &#39;Engineer&#39;}
</code></pre><p>关键字参数有什么用？它可以扩展函数的功能。比如，在<code>person</code>函数里，我们保证能接收到<code>name</code>和<code>age</code>这两个参数，但是，如果调用者愿意提供更多的参数，我们也能收到。试想你正在做一个用户注册的功能，除了用户名和年龄是必填项外，其他都是可选项，利用关键字参数来定义这个函数就能满足注册的需求。</p>
<p>和可变参数类似，也可以先组装出一个dict，然后，把该dict转换为关键字参数传进去：</p>
<pre><code>&gt;&gt;&gt; kw = {&#39;city&#39;: &#39;Beijing&#39;, &#39;job&#39;: &#39;Engineer&#39;}
&gt;&gt;&gt; person(&#39;Jack&#39;, 24, city=kw[&#39;city&#39;], job=kw[&#39;job&#39;])
name: Jack age: 24 other: {&#39;city&#39;: &#39;Beijing&#39;, &#39;job&#39;: &#39;Engineer&#39;}
</code></pre><p>当然，上面复杂的调用可以用简化的写法：</p>
<pre><code>&gt;&gt;&gt; kw = {&#39;city&#39;: &#39;Beijing&#39;, &#39;job&#39;: &#39;Engineer&#39;}
&gt;&gt;&gt; person(&#39;Jack&#39;, 24, **kw)
name: Jack age: 24 other: {&#39;city&#39;: &#39;Beijing&#39;, &#39;job&#39;: &#39;Engineer&#39;}
</code></pre><h3 id="-">参数组合</h3>
<p>在Python中定义函数，可以用必选参数、默认参数、可变参数和关键字参数，这4种参数都可以一起使用，或者只用其中某些，但是请注意，参数定义的顺序必须是：必选参数、默认参数、可变参数和关键字参数。</p>
<p>比如定义一个函数，包含上述4种参数：</p>
<pre><code>def func(a, b, c=0, *args, **kw):
    print &#39;a =&#39;, a, &#39;b =&#39;, b, &#39;c =&#39;, c, &#39;args =&#39;, args, &#39;kw =&#39;, kw
</code></pre><p>在函数调用的时候，Python解释器自动按照参数位置和参数名把对应的参数传进去。</p>
<pre><code>&gt;&gt;&gt; func(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
&gt;&gt;&gt; func(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
&gt;&gt;&gt; func(1, 2, 3, &#39;a&#39;, &#39;b&#39;)
a = 1 b = 2 c = 3 args = (&#39;a&#39;, &#39;b&#39;) kw = {}
&gt;&gt;&gt; func(1, 2, 3, &#39;a&#39;, &#39;b&#39;, x=99)
a = 1 b = 2 c = 3 args = (&#39;a&#39;, &#39;b&#39;) kw = {&#39;x&#39;: 99}
</code></pre><p>最神奇的是通过一个tuple和dict，你也可以调用该函数：</p>
<pre><code>&gt;&gt;&gt; args = (1, 2, 3, 4)
&gt;&gt;&gt; kw = {&#39;x&#39;: 99}
&gt;&gt;&gt; func(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {&#39;x&#39;: 99}
</code></pre><p>所以，对于任意函数，都可以通过类似<code>func(*args, **kw)</code>的形式调用它，无论它的参数是如何定义的。</p>
<h3 id="-">小结</h3>
<p>Python的函数具有非常灵活的参数形态，既可以实现简单的调用，又可以传入非常复杂的参数。</p>
<p>默认参数一定要用不可变对象，如果是可变对象，运行会有逻辑错误！</p>
<p>要注意定义可变参数和关键字参数的语法：</p>
<p><code>*args</code>是可变参数，args接收的是一个tuple；</p>
<p><code>**kw</code>是关键字参数，kw接收的是一个dict。</p>
<p>以及调用函数时如何传入可变参数和关键字参数的语法：</p>
<p>可变参数既可以直接传入：<code>func(1, 2, 3)</code>，又可以先组装list或tuple，再通过<code>*args</code>传入：<code>func(*(1, 2, 3))</code>；</p>
<p>关键字参数既可以直接传入：<code>func(a=1, b=2)</code>，又可以先组装dict，再通过<code>**kw</code>传入：<code>func(**{&#39;a&#39;: 1, &#39;b&#39;: 2})</code>。</p>
<p>使用<code>*args</code>和<code>**kw</code>是Python的习惯写法，当然也可以用其他参数名，但最好使用习惯用法。</p>
</div>

                    <hr style="border-top-color:#ccc" />

                    